package theorie;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

public class Grammatik{
  ArrayList<String> variable;
  ArrayList<String> terminale;
  Regelliste        regeln;
  String            startsymbol;

  /**Erzeugt eine Grammatik mit leerer Regelliste.
   * @param vars die Menge der Nonterminale.
   * @param terms die Menge der Terminale.
   * @param start das Startsymbol (aus der Menge der Nonterminale).
   */
  public Grammatik(String[] vars, String[] terms, String start){
    variable=new ArrayList<String>(vars.length);
    for(String s:vars) variable.add(s.intern());
    terminale=new ArrayList<String>(terms.length);
    for(String s:terms)
      if(variable.contains(s))
        throw new IllegalArgumentException("Terminal "+s+" is already a nonterminal.");
      else terminale.add(s.intern());
    if(!variable.contains(start.intern()))
      throw new IllegalArgumentException("Startsymbol "+start+" is not in Variables.");
    startsymbol=start.intern();
    regeln=new Regelliste();
  }

  public boolean isVariable(String s){return variable.contains(s);}

  public boolean isTerminal(String s){return terminale.contains(s);}

  public Regelliste getRegeln(){return regeln;}

  public static void main(String[] args){
    TokenSequence leer=new TokenSequence();
    System.out.println(leer);
    String[] v={"A","S"};
    String[] t={"a","b"};
    Grammatik g=new Grammatik(v,t,"S");
    Regelliste regeln=g.getRegeln();
    regeln.addRegel(new TokenSequence("S"), new TokenSequence("a"));
    regeln.addRegel(new TokenSequence("S"), new TokenSequence("S","a"));
    System.out.println(regeln);
  }

  //====================== Innere Klasse =========================
  public class Regelliste{
    public final TokenSequence START=new TokenSequence(startsymbol);
    /**Beinhaltet Regeln, die sich in ihrer linken Seite unterscheiden.
     * Auf der rechten Seite kann eine ganze Liste von TokenSequences stehen,
     * die für die verschiedenen Alternativen stehen.
     * Beispiel: links: {"a","Start","b"}
     *          rechts:{{"a","a","a"},
     *                  {"b","Start","b"}}
     * steht für a Start b -> aaa | b Start b
     */
    Hashtable<TokenSequence,ArrayList<TokenSequence>> liste
      =new Hashtable<TokenSequence, ArrayList<TokenSequence>>();

    boolean allowedTokens(TokenSequence ts, boolean links){
      boolean onevar=false;
      boolean drin;
      for(String t:ts){
        drin=isVariable(t);
        onevar|=drin;
        drin|=isTerminal(t);
        if(!drin) return false;
      }
      return onevar || !links;
    }

    /**Fügt eine Regel hinzu. Wenn die linke Seite schon bei einer anderen
     * Regel gegeben wurde, wird die neue der bereits vorhandenen linken
     * Seite als weitere Alternative hinzugefügt. Andernfalls wird die
     * linke Seite neu angelegt. Gibt es die ganze Regel schon, so wird
     * nichts getan.
     *
     * @param links die linke Seite der Regel
     * @param rechts die rechte Seite der Regel
     * @throws IllegalArgumentException wenn Grammatikregeln verletzt würden.
     */
    public void addRegel(TokenSequence links, TokenSequence rechts){
      if(!allowedTokens(links, true))
        throw new IllegalArgumentException("Illegal left side: "+links);
      if(!allowedTokens(rechts, false))
        throw new IllegalArgumentException("Illegal right side: "+rechts);
      ArrayList<TokenSequence> rechte;
      TokenSequence key=getKey(links);
      if(key==null){// wenn noch nicht drin, dann ist links neuer key
        key=links;
        rechte=new ArrayList<TokenSequence>();
        liste.put(key, rechte);
      } else rechte=liste.get(key);
      for(TokenSequence r:rechte) if(r.equals(rechts)) return;
      rechte.add(rechts);
    }

    public void removeRegel(TokenSequence links, TokenSequence rechts){
      TokenSequence key=getKey(links);
      if(key==null) return;
      ArrayList<TokenSequence> rechte=liste.get(key);
      for(TokenSequence r:rechte)
        if(r.equals(rechts))
          rechte.remove(r);
    }

    /**Gibt die linken Seiten aller Regeln zurück.
     *
     * @return ebendiese.
     */
    public TokenSequence[] getAlleLinke(){
      TokenSequence[] ret=new TokenSequence[liste.size()];
      int i=0;
      for(TokenSequence links:liste.keySet())
        ret[i++]=links;
      return ret;
    }

    /**Holt alle Regeln basierend auf der angegebenen linken Seite.
     *
     * @param links die linke Seite der gesuchten Regeln.
     * @return ein Array aller möglichen rechten Seiten.
     */
    public TokenSequence[] getRechteVon(TokenSequence links){
      TokenSequence key=getKey(links);
      if(key==null) return null;
      else{
        ArrayList<TokenSequence> rechte=liste.get(key);
        return rechte.toArray(new TokenSequence[rechte.size()]);
      }
    }

    public TokenSequence[] getRechteVonStart(){
      return getRechteVon(START);
    }

    /**Sucht eine TokenSequence als Key in im Hashtable der Regeln.
     *
     * @param links die gesuchte TokenSequence
     * @return die gefundene TokenSequence oder null, wenn nichts gefunden wird.
     */
    TokenSequence getKey(TokenSequence links){
      TokenSequence ret;
      for(Enumeration<TokenSequence> en=liste.keys(); en.hasMoreElements();)
        if(links.equals(ret=en.nextElement())) return ret;
      return null;
    }

    @Override
    public String toString(){
      StringBuffer sb=new StringBuffer();
      for(TokenSequence links:getAlleLinke()){
        sb.append(links);
        sb.append("->");
        TokenSequence[] rechte=getRechteVon(links);
        for(TokenSequence t:rechte){
          sb.append(t);
          sb.append("|");
        }
        sb.setCharAt(sb.length()-1, ',');
      }
      if(sb.length()>0) sb.deleteCharAt(sb.length()-1);
      return sb.toString();
    }
  }
}